#region License

// Dimebrain TweetSharp
// (www.dimebrain.com)
// 
// The MIT License
// 
// Copyright (c) 2009 Dimebrain
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using Dimebrain.TweetSharp.Core.Attributes;
using Dimebrain.TweetSharp.Core.Extensions;
#if !SILVERLIGHT
using Dimebrain.TweetSharp.Core.Web.Mocks;
#endif
using Dimebrain.TweetSharp.Core.Web.Query.OAuth;
using Dimebrain.TweetSharp.Model;

namespace Dimebrain.TweetSharp.Core.Web.Query
{
    /// <summary>
    /// A general-purpose web query engine. Supports asynchronous calls, caching,
    /// and dynamic header / parameter generation.
    /// </summary>
    public abstract partial class WebQueryBase
    {
        private static readonly object _sync = new object();
        private WebResponse _response;

        public IWebQueryInfo Info { get; private set; }
        public string UserAgent { get; private set; }

        public IDictionary<string, string> Headers { get; protected set; }
        public WebParameterCollection Parameters { get; protected set; }

        public WebResponse Response
        {
            get
            {
                lock (_sync)
                {
                    return _response;
                }
            }
            set
            {
                lock (_sync)
                {
                    _response = value;
                }
            }
        }

        public WebMethod Method { get; set; }
        public string Proxy { get; set; }
        public bool MockWebQueryClient { get; set; }
        public string AuthorizationHeader { get; protected set; }

        internal IEnumerable<IModel> MockGraph { get; set; }
        internal bool UseCompression { get; set; }
        internal bool UseTransparentProxy { get; set; }

        protected WebQueryBase(IWebQueryInfo info)
        {
            Info = info;
            Headers = BuildRequestHeaders();
            Parameters = BuildRequestParameters();
            ParseUserAgent();
        }

        protected virtual WebRequest BuildPostWebRequest(string url, out byte[] content)
        {
            url = AppendParameters(url);

            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

#if !SILVERLIGHT
            if (!Proxy.IsNullOrBlank())
            {
                request.Proxy = new WebProxy(Proxy);
            }
#endif
            if (!UserAgent.IsNullOrBlank())
            {
#if !SILVERLIGHT
                request.UserAgent = UserAgent;
#else
                request.Headers["User-Agent"] = UserAgent;
#endif
            }

            if (UseCompression)
            {
#if !SILVERLIGHT
                request.AutomaticDecompression = DecompressionMethods.GZip;
#else
                // todo will need decompression on response
                request.Accept = "gzip,deflate";
#endif
            }

            AppendHeaders(request);
            
#if !SILVERLIGHT
            content = Encoding.ASCII.GetBytes(url);
            request.ContentLength = content.Length;
#else
            // todo check implications
            content = Encoding.UTF8.GetBytes(url);
#endif
            return request;
        }

        protected void AppendHeaders(WebRequest request)
        {
            foreach (var header in Headers)
            {
#if !SILVERLIGHT
                request.Headers.Add(header.Key, header.Value);
#else
                request.Headers[header.Key] = header.Value;
#endif
            }
        }

        protected virtual string AppendParameters(string url)
        {
            var parameters = 0;
            foreach (var parameter in Parameters)
            {
                if (parameter is HttpPostParameter && Method != WebMethod.Post)
                {
                    continue;
                }

                url = url.Then(parameters > 0 || url.Contains("?") ? "&" : "?");
                url = url.Then("{0}={1}".FormatWith(parameter.Name, parameter.Value.UrlEncode()));
                parameters++;
            }

            return url;
        }

        private IDictionary<string, string> BuildRequestHeaders()
        {
            var headers = new Dictionary<string, string>();
            var properties = Info.GetType().GetProperties();

            Info.ParseAttributes<HeaderAttribute>(properties, headers);
            return headers;
        }

        private WebParameterCollection BuildRequestParameters()
        {
            var parameters = new Dictionary<string, string>();
            var properties = Info.GetType().GetProperties();

            Info.ParseAttributes<ParameterAttribute>(properties, parameters);

            var collection = new WebParameterCollection();
            parameters.ForEach(p => collection.Add(new WebParameter(p.Key, p.Value)));

            return collection;
        }

        private void ParseUserAgent()
        {
            var properties = Info.GetType().GetProperties();
            var count = 0;
            foreach(var property in properties)
            {
                var attributes = property.GetCustomAttributes<UserAgentAttribute>(true);
                count += attributes.Count();
                if (count > 1)
                {
                    throw new ArgumentException("Cannot declare more than one user agent per query");
                }

                if (count < 1)
                {
                    continue;
                }

                if (!UserAgent.IsNullOrBlank())
                {
                    continue;
                }

                var value = property.GetValue(Info, null);
                UserAgent = value != null ? value.ToString() : null;
            }
        }
        
        protected string HandleWebException(WebException ex)
        {
            if (ex.Response is HttpWebResponse && ex.Response != null)
            {
                Response = ex.Response;

                using (var reader = new StreamReader(Response.GetResponseStream()))
                {
                    var result = reader.ReadToEnd();
                    var responseArgs = new WebQueryResponseEventArgs(result);

                    OnQueryResponse(responseArgs);
                    return result;
                }
            }

            // not the droids we're looking for
            throw ex;
        }

        protected IWebQueryClient CreateWebQueryClient()
        {
            IWebQueryClient client;

            if(this is OAuthWebQuery)
            {
                client = CreateOAuthWebQueryClient(Headers, Parameters, UserAgent);
            }
            else
            {
                client = CreateWebQueryClient(Headers, Parameters, UserAgent, MockWebQueryClient, MockGraph, UseTransparentProxy);
            }

            client.ProxyValue = Proxy;
            client.UseCompression = UseCompression;
            return client;
        }

        private static IWebQueryClient CreateOAuthWebQueryClient(IDictionary<string, string> headers, WebParameterCollection parameters, string userAgent)
        {
            return new OAuthWebQueryClient(headers, parameters, userAgent);
        }

        private static IWebQueryClient CreateWebQueryClient(IDictionary<string, string> headers, WebParameterCollection parameters, string userAgent, bool mockClient, IEnumerable<IModel> graph, bool useTransparentProxy)
        {
            return
#if !SILVERLIGHT
                mockClient
                ? (IWebQueryClient) new WebQueryClientMock(graph) :
                new WebQueryClient(headers, parameters, userAgent);
#else
                new WebQueryClient(headers, parameters, userAgent, useTransparentProxy);
#endif
        }

        protected abstract void SetAuthorizationHeader(WebRequest request, string header);
    }
}